﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Windows.Media.Imaging;
using Microsoft.FaceSdk.Detection;
using Microsoft.FaceSdk.ImageHelper;
using Microsoft.Phone.Shell;
using System.ComponentModel;
using Microsoft.FaceSdk.Alignment;
using Microsoft.FaceSdk.Image;
using Microsoft.FaceSdk.Common;
using TrollFacing.Utils;
using System.Text;
using Microsoft.Phone.Tasks;
using Microsoft.FaceSdk.Common.Extension;

namespace TrollFacing
{
    public partial class TrollDeFacePage : PhoneApplicationPage
    {
        private WriteableBitmap _originalBitmap;
        private Image<byte> _sdkImage;
        private FaceRect[] _faces;
        private List<PointF[]> _points;

        private List<ContourInfo> _faceComponents = new List<ContourInfo>();

        private PhotoChooserTask _photoChooserTask;

        public TrollDeFacePage()
        {
            InitializeComponent();

            _photoChooserTask = new PhotoChooserTask();
            _photoChooserTask.Completed += PhotoChooserTaskCompleted;
            
            InitializeFaceComponents();

            Loaded += new RoutedEventHandler(TrollDeFace_Loaded);
        }

        private void ResetPicture()
        {
            Picture.Source = _originalBitmap;
            TrollFaceContainer.Children.Clear();
        }

        private void InitializeFaceComponents()
        {
            // Initialise la liste des contours disponibles
            foreach (var asmFaceComponent in EnumHelper.GetValues<ASMFaceComponent>())
            {
                if (asmFaceComponent == ASMFaceComponent.Unknown)
                    continue;

                var contourInfo = ASMModelConf.Lookup(asmFaceComponent);
                _faceComponents.Add(contourInfo);
            }
        }

        void TrollDeFace_Loaded(object sender, RoutedEventArgs e)
        {
            // Charge l'image de démo et l'affiche
            BitmapImage source = new BitmapImage(new Uri("/TrollFacing;component/Resources/Images/Demo/004.JPG", UriKind.Relative));
            source.CreateOptions = BitmapCreateOptions.None;

            _originalBitmap = new WriteableBitmap(source);
            Picture.Source = _originalBitmap;
        }

        private void DetectFace_Click(object sender, RoutedEventArgs e)
        {
            ExtraButtons.Visibility = System.Windows.Visibility.Collapsed;
            ResetPicture();

            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += (s, arg) =>
            {
                #region Détection des visages via Haar

                IFaceDetector detector = FaceDetectorFactory.Create(FaceDetectionType.Haar);

                // Conversion au format image du SDK
                _sdkImage = ImageConverter.SystemToSdk(_originalBitmap);

                if (_sdkImage.Width > 600 || _sdkImage.Height > 600)
                {
                    float scale = 1.0f;
                    _sdkImage = CropScale.ScaleImage(_sdkImage, 600, out scale);
                }

                // Détection de visage
                _faces = detector.Detect(_sdkImage);

                #endregion

                #region Détection des points dans ta face via ASM

                IFaceAlignmentor alignmentor = FaceAlignmentorFactory.Create(FaceAlignmentType.Asm);

                // Conversion en niveau de gris
                Image<byte> gray = new ImageGray(_sdkImage);
                var faceRects = from f in _faces select f.Rect;

                // Alignement de chaque visage
                _points = new List<PointF[]>();
                foreach (var faceRect in faceRects)
                {
                    var p = alignmentor.Align(gray, faceRect);
                    _points.Add(p);
                }

                #endregion

                #region Rendu
                
                Dispatcher.BeginInvoke(() =>
                    {
                        if (_faces.Count() > 0)
                            ExtraButtons.Visibility = System.Windows.Visibility.Visible;

                        // Rendu
                        using (var render = Renderer.Create(new ImageARGB(_sdkImage)))
                        {
                            foreach (var f in _faces)
                            {
                                PictureText.Text = string.Format("Confiance: {0:0.00}", f.Confidence);

                                render.DrawRectangle(Microsoft.FaceSdk.Image.Color.Blue, 2.0f, (RectangleF)f.Rect);
                            }

                            // Conversion du Image<byte> en WriteableBitmap
                            Picture.Source = ImageConverter.SdkToSystem(render.Image);
                        }

                        LoadingControl.Visibility = System.Windows.Visibility.Collapsed;
                    });

                #endregion

            };

            LoadingControl.Visibility = System.Windows.Visibility.Visible;
            worker.RunWorkerAsync();
        }

        #region Traces
        
        private void ShowTraces_Click(object sender, RoutedEventArgs e)
        {
            ResetPicture();

            // Rendu
            using (var render = Renderer.Create(new ImageARGB(_sdkImage)))
            {
                foreach (var shape in _points)
                {
                    foreach (var faceComponent in _faceComponents)
                    {
                        var path = Enumerable.Range(faceComponent.Index, faceComponent.Count).Select(i => shape[i]).ToList();

                        render.DrawPath(path, faceComponent.Contour, Microsoft.FaceSdk.Image.Color.Blue, 1);
                    }
                }

                // Conversion du Image<byte> en WriteableBitmap
                Picture.Source = ImageConverter.SdkToSystem(render.Image);
            }
        }

        #endregion

        #region Points

        private void ShowPoints_Click(object sender, RoutedEventArgs e)
        {
            ResetPicture();

            // Rendu
            using (var render = Renderer.Create(new ImageARGB(_sdkImage)))
            {
                foreach (var shape in _points)
                {
                    render.DrawPoints(shape, 2.0f, Microsoft.FaceSdk.Image.Color.Green);
                }

                // Conversion du Image<byte> en WriteableBitmap
                Picture.Source = ImageConverter.SdkToSystem(render.Image);
            }
        }

        #endregion

        #region Transmutation awesome face !

        private void AwesomeFacify_Click(object sender, RoutedEventArgs e)
        {
            ResetPicture();

            for (int i = 0; i < _faces.Length; i++)
            {
                DisplayAwesomeFaceElements(_points[i]);
            }
        }

        private void DisplayAwesomeFaceElements(IList<PointF> faceShape)
        {
            var coeff = Picture.ActualWidth / _sdkImage.Width;

            var leftEyeWidth = faceShape[8].DistanceTo(faceShape[12]) * (coeff + 1);
            BuildImage("/TrollFacing;component/Resources/Images/awesomeface_eye.png",
                faceShape[8].X * coeff - leftEyeWidth / 4, faceShape[8].Y * coeff - leftEyeWidth / 2, leftEyeWidth);

            var rightEyeWidth = faceShape[0].DistanceTo(faceShape[4]) * (coeff + 1);
            BuildImage("/TrollFacing;component/Resources/Images/awesomeface_eye.png",
                faceShape[4].X * coeff - rightEyeWidth / 4, faceShape[4].Y * coeff - leftEyeWidth / 2, rightEyeWidth);

            //var mouthWidth = faceShape[48].DistanceTo(faceShape[54]) * (coeff);
            //BuildImage("/TrollFacing;component/Resources/Images/awesomeface_mouth.png",
            //    faceShape[48].X * coeff, faceShape[52].Y * coeff, mouthWidth);
        }

        #endregion

        #region Transmutation troll !

        private void TransfoTroll_Click(object sender, RoutedEventArgs e)
        {
            ResetPicture();

            for (int i = 0; i < _faces.Length; i++)
            {
                DisplayMatchingTrollFace(_points[i], _faces[i]);
            }
        }

        private void DisplayMatchingTrollFace(IList<PointF> faceShape, FaceRect face)
        {
            var rightEyebrowWidth = faceShape[20].DistanceTo(faceShape[16]);
            var rightEyebrowHeight = faceShape[18].DistanceTo(faceShape[23]);

            var leftEyebrowWidth = faceShape[26].DistanceTo(faceShape[30]);
            var leftEyebrowHeight = faceShape[33].DistanceTo(faceShape[28]);

            var rightEyeWidth = faceShape[0].DistanceTo(faceShape[4]);
            var rightEyeHeight = faceShape[2].DistanceTo(faceShape[6]);
            var rightEyeRatio = rightEyeWidth / rightEyeHeight;

            var leftEyeWidth = faceShape[8].DistanceTo(faceShape[12]);
            var leftEyeHeight = faceShape[10].DistanceTo(faceShape[14]);
            var leftEyeRatio = leftEyeWidth / leftEyeHeight;

            var mouthWidth = faceShape[48].DistanceTo(faceShape[54]);
            var mouthHeight = faceShape[62].DistanceTo(faceShape[66]);
            var mouthRatio = mouthWidth / mouthHeight;

            MouthState mouthState;
            EyeBrowState eyeBrowState;
            EyeState eyeState;

            // Sourcils
            if (faceShape[23].DistanceTo(faceShape[3]) > rightEyebrowHeight * 2
                || faceShape[33].DistanceTo(faceShape[11]) > leftEyebrowHeight * 2)
            {
                eyeBrowState = EyeBrowState.Unfrown;
            }
            else if (faceShape[23].DistanceTo(faceShape[3]) < rightEyebrowHeight
                || faceShape[33].DistanceTo(faceShape[11]) < leftEyebrowHeight)
            {
                eyeBrowState = EyeBrowState.Frown;
            }
            else
            {
                eyeBrowState = EyeBrowState.Neutral;
            }

            // Yeux
            if (rightEyeRatio > 4
             && leftEyeRatio > 4)
            {
                eyeState = EyeState.OpenWide;
            }
            else if (rightEyeRatio < 1
             && leftEyeRatio < 1)
            {
                eyeState = EyeState.Closed;
            }
            else
            {
                eyeState = EyeState.OpenNeutral;
            }

            // Bouche
            if (faceShape[62].DistanceTo(faceShape[66]) < faceShape[51].DistanceTo(faceShape[62]))
            {
                if (faceShape[54].AngleTo(faceShape[66]) < 180)
                    mouthState = MouthState.ClosedUnhappy;
                else
                    mouthState = MouthState.ClosedNeutral;
            }
            else if (mouthRatio < 1)
            {
                mouthState = MouthState.OpenWideVertical;
            }
            else if (mouthRatio > 3.8)
            {
                mouthState = MouthState.OpenWideHorizontal;
            }
            else
            {
                mouthState = MouthState.OpenNeutral;
            }

            TrollDetectText.Text = string.Format("EyeBrow : {0} - Eye : {1} - Mouth : {2}", eyeBrowState, eyeState, mouthState);

            int trollIndex = 1;
            if (mouthState == MouthState.OpenWideVertical)
                trollIndex = 2;
            else if (mouthState == MouthState.OpenWideHorizontal
                && eyeState == EyeState.OpenNeutral)
                trollIndex = 3;
            else if (mouthState == MouthState.ClosedUnhappy
                && eyeState == EyeState.OpenNeutral)
                trollIndex = 4;
            else if (mouthState == MouthState.OpenWideVertical
                && eyeBrowState == EyeBrowState.Frown)
                trollIndex = 6;
            else if (mouthState == MouthState.ClosedUnhappy
                && eyeBrowState == EyeBrowState.Neutral
                && eyeState == EyeState.OpenNeutral)
                trollIndex = 8;
            else if (mouthState == MouthState.OpenWideHorizontal
                && eyeState == EyeState.OpenWide
                && eyeBrowState == EyeBrowState.Frown)
                trollIndex = 5;

            Image trollImg = new Image();
            TrollFaceContainer.Children.Add(trollImg);

            var coeff = Picture.ActualWidth / _sdkImage.Width;
            trollImg.Width = face.Rect.Width * coeff * 2;
            trollImg.Height = face.Rect.Height * coeff * 2;
            trollImg.Margin = new Thickness(face.Rect.Left * coeff - trollImg.Width / 4, face.Rect.Top * coeff - trollImg.Height / 4, 0, 0);

            var imgPath = string.Format("/TrollFacing;component/Resources/Images/trollface_{0:00}.png", trollIndex);
            trollImg.Source = new BitmapImage(new Uri(imgPath, UriKind.Relative));
        }

        #endregion

        #region Changement de photo
        
        private void ChangePhoto_Click(object sender, RoutedEventArgs e)
        {
            _photoChooserTask.Show();
        }

        private void PhotoChooserTaskCompleted(object sender, PhotoResult e)
        {
            if (e.TaskResult == TaskResult.OK)
            {
                ExtraButtons.Visibility = System.Windows.Visibility.Collapsed;
                TrollFaceContainer.Children.Clear();

                var bitmapImage = new BitmapImage();
                bitmapImage.SetSource(e.ChosenPhoto);

                _originalBitmap = new WriteableBitmap(bitmapImage);
                Picture.Source = _originalBitmap;
            }
        }

        #endregion

        private Image BuildImage(string path, double left, double top, double width)
        {
            Image img = new Image();
            var source = new BitmapImage(new Uri(path, UriKind.Relative));
            source.CreateOptions = BitmapCreateOptions.None;
            img.Source = source;
            
            img.Width = width;
            img.Height = source.PixelHeight * width / source.PixelWidth;
            img.Margin = new Thickness(left, top, 0, 0);
            img.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
            img.VerticalAlignment = System.Windows.VerticalAlignment.Top;

            TrollFaceContainer.Children.Add(img);

            return img;
        }
    }

    public enum EyeBrowState 
    {
        Frown,
        Neutral,
        Unfrown
    }

    public enum EyeState
    {
        OpenNeutral,
        OpenWide,
        Closed
    }

    public enum MouthState
    {
        OpenNeutral,
        OpenWideVertical,
        OpenWideHorizontal,
        ClosedNeutral,
        ClosedUnhappy
    }
}